﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using GameLibrary;
using GameLibrary.Settlement;
using GameLibrary.Map;

namespace UserInterface.Helpers.Graph
{
    using Model;

    [Serializable]
    public class CreateGraphFrom2DArray
    {
        private GameMapElement[,] Map;
        private Position From;
        private double[,] MoveCostMap;
        public CreateGraphFrom2DArray(
            GameMapElement[,] array, 
            Position from,  
            double[,] moveCostMap = null)
        {
            this.Map = array;
            this.From = from;
            if (moveCostMap == null)
                this.MoveCostMap = GetMoveCostMap();
            else
                this.MoveCostMap = moveCostMap;
        }

        public Graph Create()
        {
            return GetGraphFromMoveCostMap();
        }

        private Graph GetGraphFromMoveCostMap()
        {
            Graph graph = new Graph();

            for (int i = 0; i < Map.GetLength(0); i++)
                for (int j = 0; j < Map.GetLength(1); j++)
                {
                    Position pos = new Position(i, j);

                    GraphNode gn = GetGraphNode(pos, graph, MoveCostMap[i, j]);
                    graph.AddNode(gn);

                    var neighbors = GetNodeNeighbors(gn, graph);
                    graph.AddNeighborsOfOneNode(gn, neighbors);
                }

            return graph;
        }

        private GraphNode GetGraphNode(Position pos, Graph graph, double weight)
        {
            var graphNodes = from gn in graph.GraphNodes
                             where gn.Position == pos
                             select gn;
            if (graphNodes.Count() == 0)
                return new GraphNode(pos, weight);
            else
                if (graphNodes.Count() == 1)
                    return graphNodes.First();
                else
                    throw new Exception("Should Not Reach");
        }

        private IEnumerable<GraphNode> GetNodeNeighbors(GraphNode node,
                                                        Graph graph)
        {
            var positions = GetNeighborPositions(node.Position);

            var graphNodes = from n in graph.GraphNodes
                             where positions.Contains(n.Position)
                             select n;
            foreach(GraphNode gn in graphNodes)
                yield return gn;

            var positionsNotContainedInGraph = positions.Except(from n in graphNodes
                                                                select n.Position);                                                            

            foreach (Position pos in positionsNotContainedInGraph)
            {
                yield return new GraphNode(pos, MoveCostMap[pos.X, pos.Y]);
            }
        }

        private IEnumerable<Position> GetNeighborPositions(Position pos)
        {
            for(int i=-1;i<2;i++)
                for (int j = -1; j < 2; j++)
                {  
                    if (i == 0 && j == 0) // same position as pos
                        continue;

                    Position position = new Position(i, j) + pos;

                    if (PositionWithinMap(position))
                        yield return position;
                }
        }

        private bool PositionWithinMap(Position pos)
        {
            if (pos.IsLessThanZero() == false
               && pos.X < Map.GetLength(0)
               && pos.Y < Map.GetLength(1))
                return true;
            else
                return false;
        }

        #region get move cost map

        private double[,] GetMoveCostMap()
        {
            double[,] moveCostMap = new double[Map.GetLength(0), Map.GetLength(1)];
            for (int i = 0; i < moveCostMap.GetLength(0); i++)
                for (int j = 0; j < moveCostMap.GetLength(1); j++)
                    moveCostMap[i, j] = GetMoveCostOfAPosition(new Position(i, j));
            return moveCostMap;
        }

        private double GetMoveCostOfAPosition(Position pos)
        {
            var me = GetMapElementByPosition(pos);
            var from = GetMapElementByPosition(this.From);

            var currentOwnerID = this.GetOwnerID(pos);
            var fromOwnerID = this.GetOwnerID(this.From);

            if (currentOwnerID == fromOwnerID
                || currentOwnerID == 0)
            {
                double baseMoveCost = me.Tile.BaseMoveCost;
                double roadLevel = GetRoadLevel(me);
                return baseMoveCost / roadLevel;
            }
            else
                return double.PositiveInfinity;
        }

        private uint GetOwnerID(Position pos)
        {
            var me = this.GetMapElementByPosition(pos);
            if (me.Units.Count() > 0)
                return me.Units.First().OwnerID;
            if (me.Settlement != null)
                return me.Settlement.OwnerID;
            return 0;
        }

        private GameMapElement GetMapElementByPosition(Position pos)
        {
            return Map[pos.X, pos.Y];
        }

        private double GetRoadLevel(GameMapElement me)
        {
            double roadLevel = 1;
            if (me.Settlement != null)
                roadLevel += me.Settlement.Buildings[SettlementHelper.Road];
            return roadLevel;
        }

        #endregion
    }   
}
